import md5 from 'crypto-js/md5'
// keys
const sender = Symbol('sender')
const sendAck = Symbol('ack')
// const
const ACK = 'ACK' // 请求消息
const SEQ = 'SEQ' // 响应消息
const STATE = 'STATE' // 修改VUEX 消息
const CONTROLL = 'CONTROLL' // 3D控制消息
const ACTION = 'ACTION' // 执行动作消息
const INFO = 'INFO' // 普通消息
const SYS = 'SYS' // 系统消息
const RTS = 'RTS' // Real-time synchronization

const GET = 'GET'

/**
 * 是否支持WS
 */
function isSupportWS() {
  return !!WebSocket
}

/**
 * 处理返回数据
 * @param {object} msg Event
 */
function interceptors(msg = {}) {
  const {
    type,
    data,
    target: { readyState }
  } = msg
  let jsonData, message
  try {
    jsonData = JSON.parse(data)
  } catch (error) {
    message = data
  }
  return {
    type,
    data: jsonData,
    message,
    readyState
  }
}

/**
 * 事件代理类
 */
class Eventer {
  /**
   * 注册事件
   * @param {string} type 事件类型
   * @param {fn} listener
   */
  addEventListener(type, listener) {
    if (this._listeners === undefined) this._listeners = {}
    let listeners = this._listeners
    if (listeners[type] === undefined) {
      listeners[type] = []
    }

    if (listeners[type].indexOf(listener) === -1) {
      listeners[type].push(listener)
    }
  }
  /**
   * 判断是否已注册事件
   * @param {string} type
   * @param {fn} listener
   */
  hasEventListener(type, listener) {
    if (this._listeners === undefined) return false
    let listeners = this._listeners
    return (
      listeners[type] !== undefined && listeners[type].indexOf(listener) !== -1
    )
  }
  /**
   * 移除事件
   * @param {string} type 事件类型
   * @param {fn} listener
   */
  removeEventListener(type, listener) {
    if (this._listeners === undefined) return
    let listeners = this._listeners
    let listenerArray = listeners[type]
    if (listenerArray !== undefined) {
      let index = listenerArray.indexOf(listener)
      if (index !== -1) {
        listenerArray.splice(index, 1)
      }
    }
  }
  /**
   * 分发事件
   * @param {sting} type 事件累心
   * @param {event} event 事件
   */
  dispatchEvent(type, event, next) {
    if (this._listeners === undefined) return

    let listeners = this._listeners
    let listenerArray = listeners[type]
    if (listenerArray !== undefined) {
      let array = listenerArray.slice(0)
      for (let i = 0, l = array.length; i < l; i++) {
        array[i](event, next)
      }
    }
  }
}

/**
 * websocket管理类
 */
export class Remote extends Eventer {
  constructor(url, remoteId, confirm = false) {
    super()
    this.sendReslove = {}
    this.sendReject = {}
    this.sendTimer = {}
    this.sentMsgIds = [] // 已发送的消息的id
    this.url = url
    this.closed = true
    this.readyState = 3
    this._gap = 0
    this._breakpoint = []

    // 0:断开，未连接；1:只有上传；2:只有下载；3:上传和下载
    this.status = new Proxy(
      { up: 0, down: 0 },
      {
        get: function (obj, prop) {
          return obj[prop]
        },
        set: (obj, prop, value) => {
          let oldValue = obj.up + obj.down
          obj[prop] = value
          let newValue = obj.up + obj.down
          if (newValue !== oldValue) {
            this.dispatchEvent('wsStatus', newValue)
          }
          return true
        }
      }
    )
    // 是否需要确认消息
    if (typeof remoteId === 'boolean') {
      this.confirm = remoteId
    } else {
      this.confirm = confirm
    }
    // 连接的远程id
    if (typeof remoteId === 'string') {
      this.remoteId = remoteId
    } else {
      this.remoteId = ''
    }
    // 检测浏览器是否支持
    if (!isSupportWS()) {
      console.error('Your browser does not support WebSocket connections!')
      return Object.create(null)
    }
    if (/^ws/.test(url)) {
      this.connect(this.url, this.remoteId, this.confirm)
    }
  }
  /**
   * 应答函数
   * @param {string} ack md5 string
   */
  [sendAck](ack, type = ACK, msg, timestamp) {
    const data = {
      ack,
      timestamp,
      type,
      data: msg,
      confirm: true,
      screenCode: this.screenCode
    }
    this.ws.send(JSON.stringify(data))
  }
  /**
   * 私有发送方法
   * @param {any} msg 需要发送的信息
   * @param {boolean} confirm 是否需要确认
   * @param {string} type 消息类型
   */
  [sender](msg, confirm = false, type = SEQ) {
    // this.confirm = confirm
    let data = {
      seq: md5(JSON.stringify(msg) + new Date().getTime()).toString(),
      timestamp: new Date().getTime(),
      type,
      data: msg,
      confirm,
      screenCode: this.screenCode
    }
    this.status.up = 1
    return new Promise((reslove, reject) => {
      if (this.ws?.readyState === 1) {
        // 发送消息
        this.ws.send(JSON.stringify(data))
        // 保存已发送的消息
        this.sentMsgIds.push(data.seq)
        // 需要确认收到消息
        this.sendReslove[`seq_${data.seq}`] = reslove
        this.sendReject[`seq_${data.seq}`] = reject
        // 超时清除
        this.sendTimer[`seq_${data.seq}`] = setTimeout(() => {
          const callback = this.sendReslove[`seq_${data.seq}`]
          if (typeof callback === 'function') {
            delete this.sendReslove[`seq_${data.seq}`]
            delete this.sendReject[`seq_${data.seq}`]
          }
          this.status.down = 0
          reject({ message: 'timeout', id: data.seq })
        }, 3100)
      } else {
        this.status.up = 0
        this.status.down = 0
        reject({ message: 'state error', code: this.ws?.readyState })
      }
    })
  }
  /**
   * 连接
   * @param {string} url websocket url
   * @param {string} remoteId remote connetc id
   * @param {boolean} confirm need auto confirm
   */
  connect(url, remoteId, confirm = false) {
    if (!/^ws(s)?:\/\//.test(url)) {
      console.warn('Ensure the correct websocket address!')
      return Promise.reject()
    }
    if (this.readyState !== 3) {
      console.warn('You already have One WebSocket instance!')
      return Promise.reject()
    }
    this.url = url
    // 保存screen id
    const urlArr = url.split('/')
    const len = urlArr.length
    this.appCode = urlArr[len - 1]
    this.screenCode = urlArr[len - 2]
    // 是否需要确认消息
    if (typeof remoteId === 'boolean') {
      this.confirm = remoteId
    } else {
      this.confirm = confirm
    }
    // 连接的远程id
    if (typeof remoteId === 'string') {
      this.remoteId = remoteId
    } else {
      this.remoteId = ''
    }

    // 创建ws实例
    this.ws = new WebSocket(url)
    // 监听ws消息
    this.ws.addEventListener('message', (e) => {
      // 过滤消息
      const response = interceptors(e)
      // 消息类型
      const type = response?.data?.type
      // 是否需要确认
      const confirm = response?.data?.confirm
      // 时间戳
      const timestamp = response?.data?.timestamp || new Date().getTime()
      // 手动确认收到的消息
      const next = (option) => {
        const seq = response?.data?.seq
        let msg = {
          success: true
        }
        if (typeof option === 'boolean') {
          msg.success = option
        } else if (typeof option === 'object') {
          msg = option
        }
        this[sendAck](seq, ACK, msg, timestamp)
      }
      // 心跳检测
      if (response?.data?.screenCode === 'ping') {
        clearTimeout(this.serverTimeoutTimer)
        clearTimeout(this.hartbeatTimer)
        !this.closed && this.hartbeat()
        return
      }

      // 服务器发送了系统消息
      if (type === SYS) {
        this.dispatchEvent('sys', response)
        return
      }

      // 收到非标准格式的消息
      if (!response.data) {
        this.dispatchEvent('error', response)
        return
      }
      // 判断是否发送给该实例
      const toCodeId = response.data.toCodeId
      let code = this.appCode || this.screenCode
      if (toCodeId !== code) {
        return
      }
      // 处理回音
      const seq = response?.data?.seq
      let index = this.sentMsgIds.findIndex((item) => {
        return item === seq
      })
      // 回音，服务器返回了自己发送的消息
      if (index >= 0) {
        this.sentMsgIds.splice(index, 1)
        if (!confirm) {
          this.dispatchEvent(
            'serverDelay',
            new Date().getTime() - response.data.timestamp
          )
          // 系统自动确认
          clearTimeout(this.sendTimer[`seq_${seq}`])
          this.sendReslove[`seq_${seq}`](true)
          delete this.sendReslove[`seq_${seq}`]
          delete this.sendReject[`seq_${seq}`]
        }
        this.status.down = 2
        return
      }

      // 收到ACK消息
      if (type === ACK) {
        // 收到ACK消息
        const ack = response?.data?.ack // 消息id
        const success = response?.data?.data.success // 通过next调用才有该字段
        const callback = this.sendReslove[`seq_${ack}`]
        if (typeof callback === 'function') {
          if (success === false) {
            // next 失败
            this.sendReject[`seq_${ack}`](response)
          } else {
            // next 成功或者自动回复成功
            this.sendReslove[`seq_${ack}`](response)
          }
        }
        this.dispatchEvent(
          'clientDelay',
          new Date().getTime() - response.data.timestamp
        )
        // 清空定时器
        const timer = this.sendTimer[`seq_${ack}`]
        if (timer) {
          clearTimeout(timer)
        }
        delete this.sendReslove[`seq_${ack}`]
        delete this.sendReject[`seq_${ack}`]
      } else {
        // 正常发送的消息
        // const { confirm, seq } = response.data
        this.dispatchEvent('message', response, next)
        // 发送的是数据
        /* if (confirm) {
          this[sendAck](seq)
        } */
      }
    })
    // 是否成功连接
    this.readyState = this.ws.readyState
    // 'open', 'close', 'error'事件
    return new Promise((resolve, reject) => {
      const EVENT_TYPE = ['open', 'close', 'error']
      for (let i = 0; i < EVENT_TYPE.length; i++) {
        this.ws.addEventListener(EVENT_TYPE[i], (event) => {
          const { type } = event
          this.readyState = this.ws.readyState
          clearTimeout(this.reconnectTimer)
          if (type === 'open') {
            this.closed = false
            this.hartbeat()
            resolve(event)
          } else {
            !this.closed && this.readyState !== 1 && this._autoReconnect()
            this.dispatchEvent('serverDelay', NaN)
            this.dispatchEvent('clientDelay', NaN)
            this.status.up = 0
            this.status.down = 0
            reject(event)
          }
          this.dispatchEvent(EVENT_TYPE[i], event)
        })
      }
    })
  }
  /**
   * 发送消息代理 普通
   * @param {any} msg 需要发送的信息
   * @param {boolean} confirm 是否需要确认
   */
  send(msg, confirm = false) {
    return this[sender](msg, confirm, SEQ)
  }
  /**
   * 发送消息代理 修改state
   * @param {any} msg 需要发送的信息
   * @param {boolean} confirm 是否需要确认
   */
  sendState(msg, confirm = false) {
    return this[sender](msg, confirm, STATE)
  }
  /**
   * 发送消息代理 控制
   * @param {any} msg 需要发送的信息
   * @param {boolean} confirm 是否需要确认
   */
  sendControll(msg, confirm = false) {
    return this[sender](msg, confirm, CONTROLL)
  }
  /**
   * 发送消息代理 控制
   * @param {any} msg 需要发送的信息
   * @param {boolean} confirm 是否需要确认
   */
  sendAction(msg, confirm = true) {
    return this[sender](msg, confirm, ACTION)
  }
  /**
   * 发送消息代理 实时同步
   * @param {any} msg 需要发送的信息
   * @param {boolean} confirm 是否需要确认
   */
  sendRTS(msg, confirm = true) {
    return this[sender](msg, confirm, RTS)
  }
  /**
   * 发送消息代理 发送信息
   * @param {any} msg 需要发送的信息
   * @param {boolean} confirm 是否需要确认
   */
  sendInfo(msg, confirm = false) {
    return this[sender](msg, confirm, INFO)
  }
  getState(msg, confirm = true) {
    return this[sender](msg, confirm, GET)
  }
  reconnect() {
    if (this.readyState === 1) {
      this.dispatchEvent('reconnect', true)
      return
    }
    this.connect(this.url, this.remoteId, this.confirm).then(() => {
      this.dispatchEvent('reconnect', 'network')
    })
  }
  /**
   * 自动重连
   */
  _autoReconnect() {
    if (this.closed) return
    this.reconnectTimer = setTimeout(() => {
      this.dispatchEvent('reconnecting', true)
      this.connect(this.url, this.remoteId, this.confirm)
        .then(() => {
          this.dispatchEvent('reconnect', true)
        })
        .catch(() => {})
    }, this._updateGap() * 1000)
  }
  // 动态reconnect 间隔
  _updateGap() {
    if (!this._breakpoint.length) {
      for (let i = 1; i <= 5; i++) {
        let point =
          10 * (10 + (i - 1) * 5) * 100 + (this._breakpoint[i - 2] || 0)
        this._breakpoint.push(point)
      }
      return 1
    }
    let index = this._breakpoint.findIndex((item) => {
      return item > this._gap
    })
    this._gap = this._gap + (10 + index * 5) * 100
    if (!~index) {
      return 60
    }
    return 2 + index * 1
  }
  hartbeat() {
    this.hartbeatTimer = setTimeout(() => {
      this.ws.send('{"screenCode":"ping"}')
      this.serverTimeoutTimer = setTimeout(() => {
        //如果超过一定时间还没重置，说明后端主动断开了
        this.ws.close()
      }, 3000)
    }, 30000)
  }
  /**
   * close connetction
   */
  close() {
    this.closed = true
    this.ws?.close()
    clearTimeout(this.serverTimeoutTimer)
    clearTimeout(this.hartbeatTimer)
  }
}

/**
 * 单例模式的ws
 */
export const WS = (function () {
  let instance
  return function (url, remoteId, confirm = false) {
    if (instance) {
      return instance
    } else {
      return (instance = new Remote(url, remoteId, confirm))
    }
  }
})()
